package com.mapperdb.builder.annotation;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import org.apache.ibatis.builder.BuilderException;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.scripting.xmltags.DynamicSqlSource;
import org.apache.ibatis.scripting.xmltags.SqlNode;

import com.mapperdb.helper.Annt;
import com.mapperdb.util.MapperUtils;
import com.mapperdb.util.Pair;
import com.mapperdb.util.ReflectUtils;

/**
 * 自定义 ProviderSqlSource
 * @author Administrator
 *
 */
public class ProviderSqlSourceSupper implements SqlSource {
	private Annt providerAnnt;
	private MappedStatement mappedStatement;

	public ProviderSqlSourceSupper(MappedStatement ms, Annotation provider) {
		this.mappedStatement = ms;
		this.providerAnnt = Annt.use(provider);
	}

	public BoundSql getBoundSql(Object parameterObject) {
		SqlSource sqlSource = createSqlSource(parameterObject);
		return sqlSource.getBoundSql(parameterObject);
	}

	protected SqlSource createSqlSource(Object parameterObject) {
		try {
			Class<?> providerType = providerAnnt.get("type");
			String providerMethodName = providerAnnt.get("method");

			SqlSource sqlSource = null;
			try {
				Object instance = newProvider(providerType);
				Pair<Method, Object> parir = getPair(providerType, providerMethodName);
				Object arg = parir.getValue1();
				Method providerMethod = parir.getValue0();

				Class<?> returnType = providerMethod.getReturnType();
				if (returnType == Void.TYPE) {
					invoke(providerMethod, instance, arg);
				} else if (SqlNode.class.isAssignableFrom(returnType)) {
					SqlNode sqlNode = invoke(providerMethod, instance, arg);
					sqlSource = new DynamicSqlSource(mappedStatement.getConfiguration(), sqlNode);
				} else if (String.class.equals(returnType)) {
					String xmlSql = invoke(providerMethod, instance, arg);
					sqlSource = MapperUtils.createSqlSource(mappedStatement, xmlSql);
				} else {
					throw new RuntimeException("自定义Mapper方法返回类型错误,可选的返回类型为void,SqlNode,String三种!");
				}
			} catch (Exception e) {
				throw new BuilderException("Error creating SqlSource for SqlProvider. Method '" + providerMethodName
						+ "' not found in SqlProvider '" + providerType.getName() + "'.", e);
			}
			return sqlSource;
		} catch (Exception e) {
			throw new BuilderException("Error creating SqlSource for SqlProvider.  Cause: " + e, e);
		}
	}

	// 获得 Method
	protected Pair<Method, Object> getPair(Class<?> providerType, String providerMethodName) throws NoSuchMethodException {
		Method providerMethod = null;
		Object arg = null;
		try {
			providerMethod = providerType.getMethod(providerMethodName, MappedStatement.class);
			arg = mappedStatement;
		} catch (NoSuchMethodException | SecurityException e) {// ignore
			providerMethod = providerType.getMethod(providerMethodName);
		}
		return Pair.with(providerMethod, arg);
	}

	// 创建 provider
	protected Object newProvider(Class<?> providerType) throws NoSuchMethodException, InstantiationException,
			IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		try {
			return providerType.getConstructor(MappedStatement.class).newInstance(mappedStatement);
		} catch (NoSuchMethodException | SecurityException e) {// ignore
			return providerType.getConstructor().newInstance();
		}
	}

	<T> T invoke(Method m, Object obj, Object arg) {
		if (arg == null) {
			return ReflectUtils.invoke(obj, m);
		} else {
			return ReflectUtils.invoke(obj, m, arg);
		}
	}
}
